#ifndef __ROOM_H__
#define __ROOM_H__
#include"util.hpp"
#include"db.hpp"
#include"online.hpp"
#include"Log.hpp"
#include<vector>
//把enum _room_status这个类型起了新名字 _room_status;
#define BOARD_ROW 15
#define BOARD_COL 15
#define WHITE_CHESS 1
#define BLACK_CHESS 2
typedef enum _room_status{GAME_START,GAME_OVER}_room_status;
//room类功能1.handle_request 用来处理不同的请求，然后根据不同的请求调用它的处理函数，处理函数的返回值都是JsonValue，目的是为了返回给request，request接收到后
//根据接收到的res对内容进行判断
//2.handle_chess对下棋的操作进行判断（还有掉线处理）。如果出现赢家则返回id，没出现则返回0.在request中可以通过返回的值判断是否有赢得玩家，有的话则对数据库进行更新并且把房间状态改为结束
//3.handle_chat对违规词进行查询，如果查询到则这句话不返回且返回result为false
//4.broadcast用来将数据序列化后发给房间中的所有人（每个游戏都这样）。同时判断玩家是否还在房间不在则输出理由;
//5.handle_exit如果有玩家退出了，那直接broadcast，并且玩家数量-1.

//room类用于管理房间中的数据
//room_manager用来管理各个房间。
class room
{
private:
    uint64_t _room_id;
    uint64_t _white_id;
    uint64_t _black_id;
    int _player_count;
    _room_status _stat;
    std::vector<std::vector<int>> _board;
    //使用句柄(下面两个类用来房间用户管理)
    online_manager *_online_user;
    user_table *_tb_user;
private:
    //判断是否胜利的接口
    bool five(int color,int row_off,int col_off,int row,int col)
    {
        //传入的偏移量row_off col_off，如果是往上走的，那就是-1 0，如果要变成往下走的1 0，只要给偏移量取反即可
        int count = 1;
        int cur_row = row + row_off;
        int cur_col = col + col_off;
        
        //根据偏移量循环的去找直到越界或者棋子颜色不同
        while(cur_row >= 0 && cur_col >= 0 && cur_row < BOARD_ROW && cur_col < BOARD_COL && _board[cur_row][cur_col] == color)
        {
            cur_row += row_off;cur_col += col_off;
            count++;
        }
        cur_row = row - row_off;
        cur_col = col - col_off;
        
        //根据偏移量循环的去找直到越界或者棋子颜色不同
        while(cur_row >= 0 && cur_col >= 0 && cur_row < BOARD_ROW && cur_col < BOARD_COL && _board[cur_row][cur_col] == color)
        {
            cur_row -= row_off;cur_col -= col_off;
            count++;
        }
        //是有可能大于5的可能只是没连起来。
        return (count >= 5);
    }
    uint64_t check_win(int color,int row,int col)
    {
        //判断各方向的是否出现五星连珠
        if(five(color,1,0,row,col) || five(color,0,1,row,col) || five(color,1,-1,row,col) || five(color,-1,-1,row,col))
        {
            return color == WHITE_CHESS ? _white_id : _black_id;
        }
        return 0;
    }
public:
    room(uint64_t room_id,online_manager *online_user,user_table *tb_user)//棋子id不在这里初始化，而是在后续加载时加入
    :_room_id(room_id),_stat(GAME_START),_player_count(0),_online_user(online_user),_tb_user(tb_user),_board(BOARD_ROW,std::vector<int>(BOARD_COL,0))
    {
        DLOG("房间初始化完成");
    }
    ~room()
    {
        DLOG("房间已关闭");
    }
    //给外部获取这个类中内容的接口。
    uint64_t room_id(){return _room_id;}
    int room_status(){return _stat;}
    int player_count(){return _player_count;}
    void add_white_user(uint64_t id){_white_id = id;_player_count++;}
    void add_black_user(uint64_t id){_black_id = id;_player_count++;}
    uint64_t get_white_id(){return _white_id;}
    uint64_t get_black_id(){return _black_id;}
    //下棋动作处理
    //总共的optype ： put_chess and chat
    //json 默认传入的要求是内置类型，如果是其他类型需要强转为json能识别的类型
    //且从json中提取数据时要用它的接口进行类似的强转
    Json::Value handle_chess(Json::Value &req)
    {
        Json::Value res = req;
        //判断房间是否正确
        
        //处理下棋位置
        int chess_row = req["row"].asInt();
        int chess_col = req["col"].asInt();
        //判断黑白棋是否掉线
        uint64_t cur_id = req["uid"].asInt64();
        if(_online_user->is_in_room(_black_id) == false)
        {
            res["result"] = true;//有结果了说明有人赢了
            res["reason"] = "黑棋掉线，白棋赢了";
            res["winner"] = (Json::UInt64)_white_id;
            return res;
        }
        if(_online_user->is_in_room(_white_id) == false)
        {
            res["result"] = true;//有结果了说明有人赢了
            res["reason"] = "白棋掉线，黑棋赢了";
            res["winner"] = (Json::UInt64)_black_id;
            return res;
        }
        //把json value不要想太多就当成一个hash表用。
        if(_board[chess_row][chess_col] != 0)
        {
            res["result"] = false;
            res["reason"] = "这个位置已经有棋，下棋失败";
            return res;
        }
        //到这里说明下棋成功了
        //判断当前是黑棋用户还是白棋
        int cur_color = cur_id == _white_id ? WHITE_CHESS : BLACK_CHESS;
        _board[chess_row][chess_col] = cur_color;
        //往下就是判断当前是否胜利
        uint64_t winner_id = check_win(cur_color,chess_row,chess_col);
        //判断id如果等于0说明没有结果，那就是继续下
        if(winner_id != 0)
        {
            res["reason"] = "连5个了,赢";
        }
        res["result"] = true;
        res["winner"] = (Json::UInt64)winner_id;
        return res;
    }
    //聊天
    Json::Value handle_chat(Json::Value &req)
    {
        //让res == req是为了减少代码，如果写出来还得写optype等等
        Json::Value res = req;
        std::string s = req["message"].asString();
        int pos = s.find("垃圾");
        if(pos != std::string::npos)
        {
            res["result"] = false;
            res["reason"] = "包含敏感词";
            return res;
        }
        //没有找到说明没有脏话
        res["result"] = true;
        return res;
    }
    //玩家退出
    void handle_exit(uint64_t uid)
    {
        Json::Value res;
        // 如果游戏还运行时有人退出。

        if (_stat == GAME_START)
        {
            uint64_t winner_id = uid == _white_id ? _black_id : _white_id;
            res["optype"] = "put_chess";
            res["result"] = true;
            res["reason"] = "对方掉线，你赢了";
            res["room_id"] = (Json::UInt64)_room_id;
            res["uid"] = (Json::UInt64)uid;
            res["row"] = -1;
            res["col"] = -1;
            res["winner"] = (Json::UInt64)winner_id;
            uint64_t loser_id = uid == _white_id ? _white_id : _black_id;
            _tb_user->win_update(winner_id);
            _tb_user->lose_update(loser_id);
            _stat = GAME_OVER;
            //并且向房间内的人广播结果
            broadcast(res);
        }
        //游戏结束时有人退出就没上面的了，结果应该放到外面处理
        _player_count--;
        return;
    }
    //上面有这么多处理函数，但是我们并没有在选用时判断optype，所以这里写的是总的请求类型分类处理
    void handle_request(Json::Value &req)
    {
        Json::Value res = req;
        //本来应该在每个函数中都检查一次roomid，但是这里统一检查减少代码
        uint64_t room_id = req["room_id"].asInt64();
        if(room_id != _room_id) 
        {
            //说明发错房间了，因为我们服务器给全部客户端都发送，此时要判断是否是属于自己的
            res["result"] = false;
            res["reason"] = "房间号错误，请求失败";
            return broadcast(res);
        }
        //到这里说明房间对了，接下来要进行判断类型
        std::string optype = req["optype"].asString();
        if(optype == "put_chess")
        {
            res = handle_chess(req);
            //接下来就是通过res中的结果判断是否有胜者
            uint64_t winner_id = res["winner"].asUInt64();
            if(winner_id != 0)
            {
                //说明有赢家
                uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;
                _tb_user->win_update(winner_id);
                _tb_user->lose_update(loser_id);
                _stat = GAME_OVER; 
            }
        }
        else if(optype == "chat")
        {
            res = handle_chat(req);
        }
        else 
        {
            //此时是未知请求
            res["result"] = false;
            res["reason"] = "未知请求";
        }
        std::string body;
        json_util::serialize(res,body);
        //打印出当前房间出现的操作
        DLOG("房间广播操作：%s\n",body.c_str());
        return broadcast(res);
    }
    void broadcast(Json::Value &req)
    {
        //把这个信息发给目前房间中的所有人
        std::string res;
        //将json变成string用于网络传输
        json_util::serialize(req,res);
        //得到房间内所有用户的连接
        wsserver_t::connection_ptr wconn = _online_user->get_conn_from_room(_white_id);
        wsserver_t::connection_ptr bconn = _online_user->get_conn_from_room(_black_id);
        //通过连接把信息传给对方主机
        if(wconn.get() != nullptr)
        wconn->send(res);
        else DLOG("白色玩家连接异常，数据发送失败");
        if(bconn.get() != nullptr)
        bconn->send(res);
        else DLOG("黑色玩家连接异常，数据发送失败");
        return;
    }
};
using room_ptr = std::shared_ptr<room>;
//room_manager管理类
class room_manager
{
private:
    //1.房间创建的管理2.用户和房间映射的管理3.房间的销毁
    uint64_t _next_id;
    std::mutex _mutex;
    //映射表
    std::unordered_map<uint64_t,uint64_t> _user_room;
    std::unordered_map<uint64_t,room_ptr> _rooms;
    //句柄
    online_manager *_online_user;
    user_table *_tb_user;
public:
    room_manager(online_manager *om,user_table *tb)
    :_online_user(om),_tb_user(tb),_next_id(1)
    {
        DLOG("房间管理模块创建中");
    }
    ~room_manager(){DLOG("房间管理模块销毁中");}
    room_ptr room_create(uint64_t uid1,uint64_t uid2)
    {
        //首先判断两个用户是否还在线
        if(_online_user->is_in_hall(uid1) == false)
        {
            DLOG("玩家：%lu,不在大厅内",uid1);
            return room_ptr();
        }
        if(_online_user->is_in_hall(uid2) == false)
        {
            DLOG("玩家：%lu,不在大厅内",uid2);
            return room_ptr();
        }
        //两个玩家都在了
        std::unique_lock<std::mutex> lock(_mutex);
        //创建一个房间给他们
        room_ptr rp(new room(_next_id,_online_user,_tb_user));//new出来的对象应该是一个指针
        rp->add_black_user(uid1);
        rp->add_white_user(uid2);

        //建立映射关系
        _rooms.insert({_next_id,rp});
        _user_room.insert({uid1,_next_id});
        _user_room.insert({uid2,_next_id});
        _next_id++;
        return rp;
    }
    room_ptr get_room_by_rid(uint64_t rid)
    {
        //要使用容器前都要上锁
        std::unique_lock<std::mutex> lock(_mutex);
        auto find = _rooms.find(rid);
        if(find == _rooms.end())
        {
            return room_ptr();
        }
        return find->second;
    }
    room_ptr get_room_by_uid(uint64_t uid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        //想通过uid找rid
        auto uit = _user_room.find(uid);
        if(uit == _user_room.end())
        {
            DLOG("没找到房间");
            return room_ptr();
        }
        //通过rid找room
        auto rid = uit->second;
        auto room = _rooms.find(rid);
        if(room == _rooms.end())
        {
            DLOG("没找到房间");
            return room_ptr();
        }
        return room->second;
    }
    void user_exit(uint64_t uid)
    {
        //1.房间退出2.用户全部退出，房间关闭
        room_ptr rp = get_room_by_uid(uid);
        if(rp.get() == nullptr) return;

        //找到房间后，对指定用户进行退出
        //如果用户没了，那房间就关了
        rp->handle_exit(uid);
        if(rp->player_count() == 0)
        {
            room_destroy(rp->room_id());
            return;
        }
        return;
    }
    void room_destroy(uint64_t rid)
    {
        
        room_ptr rp = get_room_by_rid(rid);
        if(rp.get() == nullptr)
        {
            return;
        }
        //找到房间后，先拿到所有人的信息
        uint64_t uid1 = rp->get_black_id();
        uint64_t uid2 = rp->get_white_id();
        //让二者都退出
        std::unique_lock<std::mutex> lock(_mutex);
        _user_room.erase(uid1);
        _user_room.erase(uid2);
        _rooms.erase(rid);
        return;
    }
    
};
#endif